#include "SearchDaemon.h"
#include "Common.h"
#include "Poco/ByteOrder.h"
#include "Poco/Net/IPAddress.h"

using Poco::ByteOrder;
using Poco::Net::IPAddress;


namespace cm_nat
{
    SearchDaemon::SearchDaemon( ByteBufferPool* p_pool, SocketReactor* reactor )
        :m_bufferPool(p_pool), m_reactor(reactor), m_port(NULL),m_listener(NULL)
    {

    }

    SearchDaemon::~SearchDaemon()
    {

    }

    int SearchDaemon::setup(IPAddress bindIP, std::string username, std::string pwd)
    {
        m_username = username;
        m_pwd = pwd;

        if( !m_port ){
            m_port = new MulticastPort( *m_reactor, m_bufferPool );
        }

        m_port->setup(MulticastPort::MULTICAST_SERVER, bindIP,
            SocketAddress(MULTICAST_GROUP_IP, MULTICAST_GROUP_PORT) );

        m_port->registerListener( this );

        return 0;
    }

    int SearchDaemon::teardown()
    {

        return 0;
    }

    void SearchDaemon::registerListener( SearchDaemonListener* p_listener )
    {
        m_listener = p_listener;
    }

    void SearchDaemon::setMediaAddress( SocketAddress mediaAddress )
    {
        m_daemonMediaAddress = mediaAddress;
    }

    void SearchDaemon::onReceive(MulticastPort*p_port, ByteBuffer* p_data)
    {
         poco_debug( g_log_nat, "SearchDaemon::onReceive");

         if( detectMessage(p_data) ){
             processMessage(p_data);
         }

         SocketAddress* p_sa = (SocketAddress*)p_data->getData();
         delete p_sa;
         p_data->decreaseRef();
    }

    bool SearchDaemon::detectMessage(ByteBuffer* data)
    {
        if(data->Length()<20){
            return false;
        }

        const char* p_data = data->Data();
        char byte = p_data[0];

        if( byte & 0xc0 ){
            return false;
        }

        //message length is 4 aligns
        byte = p_data[3];
        if( byte & 0x03 ){
//            return false;
        }

        return true;
    }

    int SearchDaemon::processMessage( ByteBuffer* data )
    {
        bool res = false;
        StunMessage smsg;
        res = smsg.Read( data );

        const SocketAddress* from = (SocketAddress*)data->getData();

        poco_debug(g_log_nat, Logger::format("processMessage from address:$0", from->toString()) );

        switch( smsg.type() )
        {
        case STUN_BINDING_REQUEST:
            {
                processSearchRequest( &smsg, *from );
            }
            break;
        case STUN_SEND_REQUEST:
            {
                processAuthRequest( &smsg, *from );
            }
            break;
        default:
            break;
        }

        return true;
    }

    int SearchDaemon::processSearchRequest( StunMessage* p_sm, const SocketAddress& from )
    {
        StunMessage response;
        response.SetType(STUN_BINDING_RESPONSE);

        SocketAddress& bindAddress = m_port->getBindAddress();
        StunAddressAttribute* addr_attr = 
            StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
        addr_attr->SetFamily(1);
        addr_attr->SetPort( bindAddress.port() );

        //must get bind_ip in a temp var,or else the returned in_addr is invalid
        IPAddress bind_ip = bindAddress.host();
        const struct in_addr* ip= (struct in_addr*)bind_ip.addr();
        addr_attr->SetIP( ByteOrder::fromNetwork( (UInt32)ip->s_addr ) );
        response.AddAttribute(addr_attr);

        StunByteStringAttribute* id_attr = 
            StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
        id_attr->CopyBytes( m_username.c_str(), m_username.length() );
        response.AddAttribute( id_attr );

        ByteBuffer* p_data = NULL;
        if(m_bufferPool){
            p_data = m_bufferPool->get(0);
        }else{
            return false;
        }
        if( !p_data ){
            return false;
        }
        p_data->increaseRef();
        response.Write( p_data );

        m_port->send(p_data, from );

        return true;
    }

    int SearchDaemon::processAuthRequest( StunMessage* p_sm, const SocketAddress& from )
    {
        std::string username;
        std::string pwd;

        const StunAddressAttribute* p_ma = p_sm->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
        if( !p_ma ){
            return false;
        }
        struct in_addr  ip;
        //in Poco IPAddress, data is in Network Order
        ip.s_addr = ByteOrder::toNetwork( (UInt32)p_ma->ip() );

        SocketAddress sa( IPAddress( &ip, sizeof(in_addr)), p_ma->port() );
        m_clientMediaAddress = sa;
        poco_debug( g_log_nat, Logger::format("client media Address:$0", m_clientMediaAddress.toString()) );

        const StunByteStringAttribute* p_name_a = p_sm->GetByteString(STUN_ATTR_USERNAME);
        if( !p_name_a ){
            return false;
        }
        username = std::string( p_name_a->bytes(), p_name_a->length() );

        const StunByteStringAttribute* p_pwd_a = p_sm->GetByteString(STUN_ATTR_PASSWORD);
        if( !p_pwd_a ){
            return false;
        }
        pwd = std::string( p_pwd_a->bytes(), p_pwd_a->length() );

        poco_debug( g_log_nat, Logger::format("username from client:$0", username) );
        poco_debug( g_log_nat, Logger::format("password from client:$0", pwd) );
        
        if( m_username!=username || m_pwd!=pwd ){
            poco_debug( g_log_nat, "Wrong pwd or username");
        }

        //send authenticate result
        StunMessage response;
        response.SetType( STUN_SEND_RESPONSE );

        StunAddressAttribute* addr_attr = 
            StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);

        addr_attr->SetFamily(1);
        addr_attr->SetPort( m_daemonMediaAddress.port() );

        IPAddress media_ip = m_daemonMediaAddress.host();
        const struct in_addr* media_addr= (struct in_addr*) media_ip.addr();
        addr_attr->SetIP( ByteOrder::fromNetwork( (UInt32)media_addr->s_addr ) );
        response.AddAttribute(addr_attr);

        ByteBuffer* p_data = NULL;
        if(m_bufferPool){
            p_data = m_bufferPool->get(0);
        }else{
            return false;
        }
        if( !p_data ){
            return false;
        }
        p_data->increaseRef();
        response.Write( p_data );

        m_port->send( p_data, from);

        return true;
    }

}